Lambdaで6MBを超えるデータをReturnできなかったので、S3のPre-Signed URLを使った話
IoT機器からセンサーデータを収集しています。 そのセンサーデータをAPI Gateway(+Lambda)で扱おうとしたところ、Lambdaのレスポンス上限6MB(同期実行時)に引っかかりました。
そこで、対策としてS3バケットのPre-Signed URLを使ってみました。
おすすめの方
- LambdaでPre-Signed URLを使いたい方
適当なデータを作成する
サンプルデータ
下記のようなデータを作成します。
{ "aaa": [ [1619600735000, 11.1], [1619600737000, 22.1], [1619600739000, 33.1] ], "bbb": [ [1619600735000, 11.2], [1619600737000, 22.2], [1619600739000, 33.2] ], "ccc": [ [1619600735000, 11.3], [1619600737000, 22.3], [1619600739000, 33.3] ], "ddd": [ [1619600735000, 11.4], [1619600737000, 22.4], [1619600739000, 33.4] ], "eee": [ [1619600735000, 11.5], [1619600737000, 22.5], [1619600739000, 33.5] ] }
データ作成用のプログラム
import json import sys from random import random def make_data(number): aaa = [] bbb = [] ccc = [] ddd = [] eee = [] # タイムスタンプは固定にしておく timestamp = 1619600735000 for i in range(number): aaa.append([timestamp, round(random() * 100, 1)]) bbb.append([timestamp, round(random() * 100, 1)]) ccc.append([timestamp, round(random() * 100, 1)]) ddd.append([timestamp, round(random() * 100, 1)]) eee.append([timestamp, round(random() * 100, 1)]) data = json.dumps({ 'aaa': aaa, 'bbb': bbb, 'ccc': ccc, 'ddd': ddd, 'eee': eee, }) print(data) if __name__ == '__main__': if len(sys.argv) != 2: print('please data count: $ python make_data.py 3') else: make_data(int(sys.argv[1]))
データを作成する
python make_data.py 100000 > data_100000.json
S3バケットに格納する
aws s3 cp data_100000.json s3://cm-fujii.genki-test
S3オブジェクトのファイルサイズ
6MBを超えていました。
- data_100000.json: 10.9 [MB]
まずは、Lambdaが6MBを超えるデータをReturnできないことを確認する
sam init
sam init \ --runtime python3.8 \ --name Lambda-Pre-Signed-Test \ --app-template hello-world \ --package-type Zip
SAMテンプレート
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Lambda-Pre-Signed-Test Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.8 Timeout: 10 Policies: - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess Events: HelloWorld: Type: Api Properties: Path: /hello Method: get HelloWorldFunctionLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${HelloWorldFunction} Outputs: HelloWorldApi: Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
Lambdaコード
import json import boto3 S3_BUCKET_NAME = 'cm-fujii.genki-test' S3_OBJECT_KEY = 'data_100000.json' s3 = boto3.resource('s3') def lambda_handler(event, context): obj = s3.Object(S3_BUCKET_NAME, S3_OBJECT_KEY) data = obj.get()['Body'].read().decode('utf-8') print(obj) print(len(data) / 1024 / 1024) return { "statusCode": 200, "body": data, }
デプロイ
sam build sam deploy \ --template-file template.yaml \ --stack-name Lambda-Pre-Signed-Test-Stack \ --s3-bucket cm-fujii.genki-deploy \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset
APIエンドポイントを取得
$ aws cloudformation describe-stacks \ --stack-name Lambda-Pre-Signed-Test-Stack \ --query 'Stacks[].Outputs' [ [ { "OutputKey": "HelloWorldApi", "OutputValue": "https://awvil3t6nc.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/", } ] ]
APIアクセスすると、Internal server error になる
$ curl https://awvil3t6nc.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ {"message": "Internal server error"}
CloudWatch Logの様子
下記のメッセージが残っていました(抜粋)。
LAMBDA_RUNTIME Failed to post handler success response. Http response code: 413. RuntimeError: Failed to post invocation response Response payload size (11450971 bytes) exceeded maximum allowed payload size (6291556 bytes). Function.ResponseSizeTooLarge
Lambdaの最大レスポンスサイズ(6MB)より多いデータは返せませんでした。
Pre-Signed URLを使う
Lambdaコード
Lambdaコードを書き換えて、Pre-Signed URLを返すようにします。
import json import boto3 S3_BUCKET_NAME = 'cm-fujii.genki-test' S3_OBJECT_KEY = 'data_100000.json' s3 = boto3.client('s3') def lambda_handler(event, context): url = s3.generate_presigned_url( 'get_object', Params={ 'Bucket': S3_BUCKET_NAME, 'Key': S3_OBJECT_KEY }, ExpiresIn=3600 ) return { "statusCode": 200, "body": url, }
ExpiresIn
は1時間(3600秒)としましたが、6時間を超える値を設定する場合は、適切な認証情報を用いてください。
HTTPステータスは200
を返すようにしていますが、303
でも良さそうです。
デプロイ
sam build sam deploy \ --template-file template.yaml \ --stack-name Lambda-Pre-Signed-Test-Stack \ --s3-bucket cm-fujii.genki-deploy \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset
APIアクセスすると、Pre-Signed URLを取得できる
$ curl https://awvil3t6nc.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ https://s3.ap-northeast-1.amazonaws.com/cm-fujii.genki-test/data_100000.json?AWSAccessKeyId\=xxxxx
※\=
部分は=
だけです。(\=
にしないと、本ブログ上で表示されませんでした)
S3オブジェクトを取得する
$ curl -o s3_object.json "https://s3.ap-northeast-1.amazonaws.com/cm-fujii.genki-test/data_100000.json?AWSAccessKeyId\=xxxxx"
バッチリ取得できました!!
$ ls -lh s3_object.json *** 11M *** s3_object.json